---
id: dynamic-api-controller
title: 3. 动态 WebAPI
sidebar_label: 3. 动态 WebAPI
---

import useBaseUrl from "@docusaurus/useBaseUrl";
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

## 3.1 什么是控制器

简单来说，控制器是一个承上启下的作用，根据用户输入，执行响应行为（动作方法），同时在行为中调用模型的业务逻辑，返回给用户结果（视图）。

<img src={useBaseUrl("img/kzq.png")} />

<p></p>

在 `ASP.NET Core` 中，控制器有两种表现形式：

- `Mvc`（带视图）
- `WebAPI`（RESTful API）

<Tabs
  defaultValue="mvc"
  values={[
    { label: "Mvc 控制器", value: "mvc" },
    { label: "WebAPI 控制器", value: "webapi" },
  ]}
>
  <TabItem value="mvc">


```cs {1,5,7}
using Microsoft.AspNetCore.Mvc;

namespace Fur.Web.Entry.Controllers
{
    public class MvcController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}
```

  </TabItem>
  <TabItem value="webapi">


```cs {1,5,6,8,9}
using Microsoft.AspNetCore.Mvc;

namespace Fur.Web.Entry.Controllers
{
    [Route("api/[controller]")]
    public class WebApiController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Content(nameof(Fur));
        }
    }
}
```

  </TabItem>
</Tabs>


`Mvc` 控制器和 `WebAPI` 控制器最大的区别是 `WebAPI` 控制器不带 **视图** 和通过 **请求谓词和路由地址响应行为**。

## 3.2 `Mvc 控制器` 约定和缺点

在学习动态 `WebAPI` 控制器之前，首先了解 `ASP.NET Core` 中 `WebAPI` 的一些约定和注意事项。

### 3.2.1 `WebAPI` 约定

在 `ASP.NET Core` 应用中，一个 `WebAPI` 控制器需遵循以下约定：

- 控制器类**必须继承 `ControllerBase` 或间接继承**
- 动作方法**必须贴有 `[HttpMethod]` 特性，如：`[HttpGet]`**
- 控制器或动作方法**至少有一个配置 `[Route]` 特性**
- 生成 `WebAPI` 路由地址时会自动去掉控制器名称 `Controller` 后缀，同时也会去掉动作方法匹配的 `HttpVerb` 谓词，如 `GET，POST，DELETE，PUT` 等
- **不支持返回非 `IEnumerable<T>` 泛型对象**
- **不支持类类型参数在 `GET，HEAD` 请求下生成 `Query` 参数**

除了上述约定外，`WebAPI` 路由地址**基本靠手工完成**，不利于书写，不利于维护，再者，在移动应用对接中难以进行多版本控制。

### 3.2.2 `.NET Core WebAPI` 缺点

通过上一章节可以看出，`ASP.NET Core` 应用实现 `WebAPI` 需要遵循种种约定，而且容易出错。

除了这些约定，`.NET Core WebAPI` 有以下缺点：

- 路由地址基本靠手工完成
- 在现在移动为王的时代，不利于进行多版本控制
- 对接 `Swagger` 文档分组比较复杂
- 实现 `Policy` 策略授权也比较复杂
- 不支持控制器热拔插插件化
- 难以实现复杂自定义的 `RESTful API` 风格

## 3.3 动态 `WebAPI` 控制器

针对以上 `ASP.NET Core` 提供的 `WebAPI` 必须遵循的约定和不可避免的缺点，`Fur` 框架创造出一种更加灵活创建 `WebAPI` 控制器的方式。

这个方式在继承了 `ASP.NET Core WebAPI` 所有优点，同时进行了大量拓展和优化。优化后的 `WebAPI` 具有以下优点：

- 具备原有的 `ControllerBase` 所有功能
- 支持**任意公开 非静态 非抽象 非泛型类**转控制器
- 提供更加灵活方便的 `IDynamicApiController` 空接口或 `[DynamicApiController]` 特性替代 `ControllerBase` 抽象类
- 无需手动配置 `[HttpMethod]` 特性，同时支持一个动作方法多个 `HttpVerb`
- 无需手动配置 `[Route]` 特性，支持更加灵活的配置及自动路由生成
- 支持返回泛型接口，泛型类
- 和 `Swagger` 深度结合，提供极其方便的创建 `Swagger` 分组配置
- 支持 `Basic Auth，Jwt，ApiKey` 等多种权限灵活配置
- 支持控制器、动作方法**版本控制**功能
- 支持 `GET、HEAD` 请求自动转换 `类类型参数`
- 支持生成 `OAS3` 接口规范

## 3.4 动态 `WebAPI` 使用

### 3.4.1 注册动态 `WebAPI` 服务

在 `Fur` 框架中，动态 `WebAPI` 服务需要在 `Fur.Web.Entry/Startup.cs` 中注册。如：

```cs {4,20-21} title="Fur.Web.Entry/Startup.cs"
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Fur.Web.Entry
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddApp();
            services.AddControllers()
                    .AddDynamicApiControllers();
            // Other Codes...
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            // Other Codes...
        }
    }
}
```

:::caution 特别注意

`.AddDynamicApiControllers()` 必须在 `services.AddControllers()` 之后注册。

:::

## 3.5 第一个例子

创建一个 `FurAppService` 类继承 `IDynamicApiController` 接口 或 贴 `[DynamicApiController]` 特性，并在这个类中编写一个 `Get` 方法。

- **`IDynamicApiController` 方式**

```cs {1,5,7}
using Fur.DynamicApiController;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        public string Get()
        {
            return $"Hello {nameof(Fur)}";
        }
    }
}
```

- **`[DynamicApiController]` 方式**

```cs {1,5,8}
using Fur.DynamicApiController;

namespace Fur.Application
{
    [DynamicApiController]
    public class FurAppService
    {
        public string Get()
        {
            return $"Hello {nameof(Fur)}";
        }
    }
}
```

如下图所示，一个 `WebAPI` 接口就这么生成了。

<img src={useBaseUrl("img/dyglz.gif")} />

## 3.6 动态 `WebAPI` 原理解析

### 3.6.1 控制器特性提供器

`Fur` 框架会在应用启动时注册 `DynamicApiControllerFeatureProvider` 控制器特性提供器，该提供器继承自 `ControllerFeatureProvider` 类。

接着重写 `bool IsController(TypeInfo typeInfo)` 方法，用来标识控制器类型。在 `Fur` 框架中，**继承自 `ControllerBase` 类或 `IDynamicApiController` 接口或 `[DynamicApiController]` 特性都会被标记为控制器类型。**

### 3.6.2 应用模型转换器

`Fur` 框架同时在应用启动时注册 `DynamicApiControllerApplicationModelConvention` 应用模型转换器，该转换器继承自 `IApplicationModelConvention` 接口。

接着实现 `void Apply(ApplicationModel application)` 接口方法。在该方法中配置控制器名称、路由、导出可见性及动作方法名称、路由、导出可见性等。

实际上该方法做的就是按照 **[WebAPI 约定](#321-webapi-约定)** 提前帮我们配置好路由、请求谓词等信息。避免了手动配置的同时还增加了许多新特性，如**版本控制。**

## 3.7 动态 `WebAPI` 配置约定

### 3.7.1 控制器默认约定

- 生成控制器名称默认去除以 `AppServices，AppService，ApiController，Controller，Services，Service` 作为前后缀的字符串。见第一个例子中的 `FurAppService -> Fur` **支持自定义配置**
- 控制器名称带 `V[0-9_]` 结尾的，会自动生成控制器版本号，如 `FurAppServiceV2 -> Fur@2`，`FurAppServiceV1_1_0 -> Fur@1.1.0`。**支持版本分隔符配置**
- 控制名称以 `骆驼命名（CamelCase）` 会自动切割成多个单词 `-` 连接。**支持自定义配置**

### 3.7.2 动作方法默认约定

- 生成的动作方法名称默认去除以 `Post/Add/Create/Insert/Submit，GetAll/GetList/Get/Find/Fetch/Query/Search，Put/Update，Delete/Remove/Clear，Patch` 开头的字符串。**支持自定义配置**
- 生成的动作方法名称默认去除以 `Async` 作为前后缀的字符串。**支持自定义配置**
- 动作方法名称带 `V[0-9_]` 结尾的，会自动生成动作方法版本号，如 `ChangePasswordV2 -> ChangePassword@2`，`ChangePasswordV1_1_0 -> ChangePassword@1.1.0`。**支持版本分隔符配置**
- 动作方法名称以 `骆驼命名（CamelCase）` 会自动切割成多个单词 `-` 连接。**支持自定义配置**
- 动作方法参数将自动转为小写。**支持自定义配置**

### 3.7.3 请求谓词默认约定

- 动作方法名
  - 以 `Post/Add/Create/Insert/Submit` 开头，则添加 `[HttpPost]` 特性。
  - 以 `GetAll/GetList/Get/Find/Fetch/Query/Search` 开头，则添加 `[HttpGet]` 特性。
  - 以 `Put/Update` 开头，则添加 `[HttpPut]` 特性。
  - 以 `Delete/Remove/Clear` 开头，则添加 `[HttpDelete]` 特性。
  - 以 `Patch` 开头，则添加 `[HttpPatch]` 特性
  - **支持自定义配置**
- 如果不在上面约定中，则默认添加 `[HttpPost]` 特性。**支持自定义配置**

### 3.7.4 路由地址默认约定

- 默认以 `api` 开头。**支持自定义配置**
- 默认转换为小写路由地址。**支持自定义配置**
- 生成控制器路由模板格式为：`api/前置参数列表/模块名或默认区域名/[controller@版本号]/后置参数列表`
- 生成动作方法路由模板格式为：`前置参数列表/模块名/[action@版本号]/后置参数列表`

### 3.7.5 其他约定

- 默认不处理 `ControllerBase` 控制器类型。**支持自定义配置**
- 默认不处理 `GET，HEAD` 请求的引用类型参数。**支持自定义配置**

## 3.8 更多例子

### 3.8.1 多种请求谓词方法

```cs {7,12,17,22,27}
using Fur.DynamicApiController;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        public string Get()
        {
            return $"GET 请求";
        }

        public string Post()
        {
            return $"POST 请求";
        }

        public string Delete()
        {
            return $"DELETE 请求";
        }

        public string Put()
        {
            return $"PUT 请求";
        }

        public string Patch()
        {
            return $"PATCH 请求";
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/dgqqwc.png")} />

### 3.8.2 多个自定义动作方法

```cs {7,12,17}
using Fur.DynamicApiController;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        public string GetVersion()
        {
            return $"v1.0.0";
        }

        public string ChangeProfile()
        {
            return "修改成功";
        }

        public string DeleteUser()
        {
            return "删除成功";
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/dzmc.png")} />

### 3.8.3 带参数动作方法

```cs {7,12,17}
using Fur.DynamicApiController;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        public string GetUser(int id)
        {
            return $"{id}";
        }

        public string GetUser(int id, string name)
        {
            return $"{id} {name}";
        }

        public TestDto Add(TestDto testDto)
        {
            return testDto;
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/dcsff.gif")} />

### 3.8.5 `GET/HEAD` 类类型参数

默认情况下，`ASP.NET Core` 会将 `GET/HEAD` 请求中的 `类类型参数` 设置为 `[FromBody]` 绑定，如：

```cs {7}
using Fur.DynamicApiController;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        public TestDto GetTest(TestDto testDto)
        {
            return testDto;
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/getyycs.png")} />

但是，`GET、HEAD` 请求不支持 `From Body` 绑定。所以我们需要转换为 `Query` 查询参数。

`Fur` 框架支持以下两种方式配置：

<Tabs
  defaultValue="fromquery"
  values={[
    { label: "[FromQuery] 特性", value: "fromquery" },
    {
      label: "配置 DynamicApiControllerSettings",
      value: "DynamicApiControllerSettings",
    },
  ]}
>
  <TabItem value="fromquery">


```cs {2,8}
using Fur.DynamicApiController;
using Microsoft.AspNetCore.Mvc;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        public TestDto GetTest([FromQuery] TestDto testDto)
        {
            return testDto;
        }
    }
}
```

  </TabItem>
  <TabItem value="DynamicApiControllerSettings">


```json {2-4} title="Fur.Web.Entry/appsettings.json"
{
  "AppSettings": {
    "DynamicApiControllerSettings": {
      "ModelToQuery": true
    }
  }
}
```

  </TabItem>
</Tabs>


如下图所示：

<img src={useBaseUrl("img/modeltoquery.png")} />

### 3.8.6 自定义参数位置

`Fur` 框架提供了非常方便的自定义参数位置的特性 `[ApiSeat]`，通过 `[ApiSeat]` 可配置参数位置，支持以下四种位置：

- `ApiSeats.ControllerStart`：控制器之前
- `ApiSeats.ControllerEnd`：控制器之后
- `ApiSeats.ActionStart`：动作方法之前
- `ApiSeats.ActionEnd`：动作方法之后。**默认值**

```cs {8-9,15-20}
using Fur.DynamicApiController;
using System;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        // 参数默认为 ApiSeats.ActionEnd
        public string RouteSeat(int id, string name)
        {
            return "配置路由参数位置";
        }

        public string RouteSeat(
            [ApiSeat(ApiSeats.ControllerStart)] int id, // 控制器名称之前
            [ApiSeat(ApiSeats.ControllerEnd)] string name, // 控制器名称之后
            [ApiSeat(ApiSeats.ControllerEnd)] int age, // 控制器名称之后
            [ApiSeat(ApiSeats.ActionStart)] decimal weight, // 动作方法名称之前
            [ApiSeat(ApiSeats.ActionStart)] float height, // 动作方法名称之前
            [ApiSeat(ApiSeats.ActionEnd)] DateTime birthday) // 动作方法名称之后（默认值）
        {
            return "配置路由参数位置";
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/cswz.png")} />

:::note 温馨提示

多个 **`同位置`** 配置的参数将按照 **`定义参数顺序`** 进行排序。

:::

:::caution 特别注意

`[ApiSeat]` 只能应用于贴了 `[FromRoute]` 特性的参数或 `基元类型、值类型、可空基元类型和可空值类型`。

:::

### 3.8.7 自定义请求谓词

```cs {2,8}
using Fur.DynamicApiController;
using Microsoft.AspNetCore.Mvc;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        [HttpPost]
        public string GetVersion()
        {
            return "1.0.0";
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/zdywc.png")} />

### 3.8.8 支持多个谓词

```cs {2,8}
using Fur.DynamicApiController;
using Microsoft.AspNetCore.Mvc;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        [HttpPost, HttpGet, AcceptVerbs("PUT", "DELETE")]
        public string GetVersion()
        {
            return "1.0.0";
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/dgwc.png")} />

:::caution 特别注意

如果动作方法中含有 `类类型参数`，且含有 `POST/PUT/DELETE` 任意请求谓词，那么该参数会自动添加 `[FromBody]` 参数，即使在 `GET/HEAD` 请求中不支持。

:::

### 3.8.9 支持自定义路由

支持控制器和动作方法自定义路由：

<Tabs
  defaultValue="kzqrl"
  values={[
    { label: "自定义控制器路由", value: "kzqrl" },
    { label: "自定义动作方法路由", value: "dzffrl" },
    { label: "同时自定义路由", value: "allrl" },
    { label: "谓词自定义路由", value: "vcrl" },
  ]}
>
  <TabItem value="kzqrl">


```cs {2,6}
using Fur.DynamicApiController;
using Microsoft.AspNetCore.Mvc;

namespace Fur.Application
{
    [Route("customapi/mobile/[controller]")]
    public class FurAppService : IDynamicApiController
    {
        public string GetVersion()
        {
            return "1.0.0";
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/kzqrl.png")} />

  </TabItem>
  <TabItem value="dzffrl">


```cs {2,8}
using Fur.DynamicApiController;
using Microsoft.AspNetCore.Mvc;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        [Route("customapi/[action]")]
        public string GetVersion()
        {
            return "1.0.0";
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/dzffrl.png")} />

  </TabItem>
  <TabItem value="allrl">


```cs {2,6,9}
using Fur.DynamicApiController;
using Microsoft.AspNetCore.Mvc;

namespace Fur.Application
{
    [Route("customapi/mobile/[controller]")]
    public class FurAppService : IDynamicApiController
    {
        [Route("get/[action]")]
        public string GetVersion()
        {
            return "1.0.0";
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/allrl.png")} />

  </TabItem>
  <TabItem value="vcrl">


```cs {9}
using Fur.DynamicApiController;
using Microsoft.AspNetCore.Mvc;

namespace Fur.Application
{
    [Route("api/[controller]")]
    public class FurAppService : IDynamicApiController
    {
        [HttpGet("get/[action]")]
        public string GetVersion()
        {
            return "1.0.0";
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/wcrl.png")} />

  </TabItem>
</Tabs>


:::important 小提示

动态方法自定义路由如果以 **`/`** 开头，则不会合并控制器路由。

:::

:::tip 推荐配置

自定义路由如果需要用到 **控制器/动作方法名称**，推荐使用 `[controller]` 或 `[action]` 占位符，因为该占位符已经自动处理了 **前后缀、版本号、模块名称**等。

:::

### 3.8.10 多路由随意组合

`Fur` 框架提供了非常灵活的各种路由组合方式，支持一对多，多对多路由组合：

```cs {6-8,11-14}
using Fur.DynamicApiController;
using Microsoft.AspNetCore.Mvc;

namespace Fur.Application
{
    [Route("api/[controller]")]
    [Route("api/[controller]/second")]
    [Route("api/[controller]/three")]
    public class FurAppService : IDynamicApiController
    {
        [HttpGet]
        [HttpGet("get/[action]")]
        [HttpPost]
        [HttpPost("post/cus-version")]
        public string GetVersion()
        {
            return "1.0.0";
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/dlrzh.gif")} />

:::caution 特别注意

动作方法不能同时贴 `[Route]` 和 `[HttpMethod]` 特性，只能二取一。

:::

### 3.8.11 支持版本控制

<Tabs
  defaultValue="kzqbb"
  values={[
    { label: "控制器版本", value: "kzqbb" },
    { label: "动作方法版本", value: "dzffbb" },
  ]}
>
  <TabItem value="kzqbb">


```cs {5,13,21}
using Fur.DynamicApiController;

namespace Fur.Application
{
    public class FurAppServiceV1 : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }
    }

    public class FurAppServiceV1_2 : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }
    }

    public class FurAppServiceV1_2_1 : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/kzqbb.png")} />

  </TabItem>
  <TabItem value="dzffbb">


```cs {7,12,16}
using Fur.DynamicApiController;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        public string GetV1()
        {
            return nameof(Fur);
        }
        public string GetV2_1()
        {
            return nameof(Fur);
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/dzffbb.png")} />

  </TabItem>
</Tabs>


:::note 版本生成原理

**`V[0-9_]`** 结尾的命名自动解析成版本号，如 **`FurAppServiceV2 -> Fur@2`**。

:::

:::tip 版本复写

除了通过特定后缀方式以外，版本还直接通过 `[ApiDescriptionSettings]` 进行复写。如：

```cs {1,2}
[ApiDescriptionSettings(Version = "3.0")]
public string GetV1()
{
    return nameof(Fur);
}
```

这时，生成版本将采用 `3.0` 替代 `1`

:::

### 3.8.12 不公开控制器或动作方法

有些时候，我们无需导出某个动作方法或控制器，只需要添加 `[ApiDescriptionSettings(false)]` 或 `[ApiExplorerSettings(IgnoreApi = true)]`即可。

另外动作方法还支持 `[NonAction]` 标记。

:::tip 推荐使用

推荐控制器或动作方法设置不导出使用 `[ApiDescriptionSettings(false)]` 特性。该特性默认继承自 `ApiExplorerSettingsAttribute` 类。

:::

## 3.9 `[ApiDescriptionSettings]`

除了上述 `ASP.NET Core` 提供的配置外，`Fur` 框架还提供了非常强大且灵活的 `[ApiDescriptionSettings]` 特性。

### 3.9.1 内置配置

- `Name`：自定义控制器/动作方法名称，`string`，默认 `null`
- `KeepName`：是否保持原有名称不处理，`bool`，默认 `false`
- `SplitCamelCase`：切割骆驼命名，`bool`，默认 `true`
- `KeepVerb`：是否保留动作方法请求谓词，`bool`，默认 `false`
- `Enabled`：是否导出接口，`bool`，默认 `true`
- `Module`：模块名，`string`，默认 `null`
- `Version`：版本号，`string`，默认 `null`
- `Groups`：接口分组，可结合 `Swagger` 一起使用，`string[]`，默认 `null`
- `Tags`：接口标签，可结合 `Swagger` 一起使用，`string[]`，默认 `null`
- `AuthPolicies`：授权策略，可结合 `授权，JWT` 一起使用，`string[]`，默认 `null`

### 3.9.2 `Name` 配置

`Name` 参数可以覆盖动态 `WebAPI` 自动生成的控制器或动作方法名称。如：

```cs {5,8}
using Fur.DynamicApiController;

namespace Fur.Application
{
    [ApiDescriptionSettings(Name = "MyFur")]
    public class FurAppService : IDynamicApiController
    {
        [ApiDescriptionSettings(Name = "MyGet")]
        public string Get()
        {
            return nameof(Fur);
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/namepz.png")} />

### 3.9.3 `KeepName` 配置

`KeepName` 参数可以保留原有的控制器或动作方法名称。如：

```cs {5,8}
using Fur.DynamicApiController;

namespace Fur.Application
{
    [ApiDescriptionSettings(KeepName = true)]
    public class FurAppService : IDynamicApiController
    {
        [ApiDescriptionSettings(KeepName = true)]
        public string Get()
        {
            return nameof(Fur);
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/keepnamepz.png")} />

### 3.9.4 `SplitCamelCase` 配置

`SplitCamelCase` 参数默认将骆驼命名切割成多个单词并通过指定 `占位符` 连接起来。默认 `占位符` 为 `-`。默认为 `true`。如：

```cs {5,8}
using Fur.DynamicApiController;

namespace Fur.Application
{
    [ApiDescriptionSettings(SplitCamelCase = false)]
    public class MyFurAppService : IDynamicApiController
    {
        [ApiDescriptionSettings(SplitCamelCase = true)]
        public string ChangeUserName()
        {
            return nameof(Fur);
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/splitnamepz.png")} />

:::important 特别注意

`KeepName` 优先级高于 `SplitCamelCase`，也就是 `KeepName` 设置为 `true`，则不会处理 `SplitCamelCase` 参数。

:::

### 3.9.5 `KeepVerb` 配置

`KeepVerb` 参数作用于动作方法，标识是否保留动作谓词。如：

```cs {7}
using Fur.DynamicApiController;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        [ApiDescriptionSettings(KeepVerb = true)]
        public string GetVersion()
        {
            return nameof(Fur);
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/keepverbpz.png")} />

### 3.9.6 `Enabled` 配置

`Enabled` 参数配置接口是否导出。通常用于动作方法，如果用于控制器实际作用不大。

```cs {12}
using Fur.DynamicApiController;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        public string GetVersion()
        {
            return nameof(Fur);
        }

        [ApiDescriptionSettings(false)]
        public string NoExport()
        {
            return nameof(Fur);
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/enablepz.png")} />

### 3.9.7 `Module` 配置

`Module` 参数可以配置路由分离，类似于 `Mvc 区域` 的作用。

```cs {5,8}
using Fur.DynamicApiController;

namespace Fur.Application
{
    [ApiDescriptionSettings(Module = "mobile")]
    public class FurAppService : IDynamicApiController
    {
        [ApiDescriptionSettings(Module = "user")]
        public string GetVersion()
        {
            return nameof(Fur);
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/modulepz.png")} />

### 3.9.8 `Version` 配置

`Version` 参数可以配置接口版本，同时又可以复写特殊版本命名配置。默认版本号分隔符为 `@`。如：

```cs {5,9-10}
using Fur.DynamicApiController;

namespace Fur.Application
{
    [ApiDescriptionSettings(Version = "1.0")]
    public class FurAppService : IDynamicApiController
    {
        // V2.0.0 被复写成 V2.1.1
        [ApiDescriptionSettings(Version = "2.1.1")]
        public string GetVersionV2_0_0()
        {
            return nameof(Fur);
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/versionpz.png")} />

### 3.9.9 `Groups` 配置

`Groups` 配置主要用于配置 `Swagger` 分组信息。

通过配置 `Groups` 参数可以将`控制器和动作方法` 进行归类和多个分组直接共享。可通过 `[ApiDescriptionSettings(params Groups)]` 构造函数传入或指定 `Groups` 参数配置接口是否导出。通常用于动作方法，如果用于控制器实际作用不大。

```cs {5,13}
using Fur.DynamicApiController;

namespace Fur.Application
{
    [ApiDescriptionSettings("Default", "Common")]
    public class FurAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        [ApiDescriptionSettings("Custom")]
        public int Get(int id)
        {
            return id;
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/dfz.gif")} />

### 3.9.10 `Tag` 配置

`Tag` 配置主要用于配置 `Swagger` 标签分组信息及合并标签。也就是 `组中组`:

<Tabs
  defaultValue="tag1"
  values={[
    { label: "标签命名", value: "tag1" },
    { label: "合并标签", value: "tag2" },
  ]}
>
  <TabItem value="tag1">


#### 未贴标签之前

```cs
using Fur.DynamicApiController;

namespace Fur.Application
{
    public class FurAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        public int Get(int id)
        {
            return id;
        }
    }

    public class TestAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        public int Get(int id)
        {
            return id;
        }
    }
}
```

#### 贴标签之后

```cs {5,19}
using Fur.DynamicApiController;

namespace Fur.Application
{
    [ApiDescriptionSettings(Tag = "分组一")]
    public class FurAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        public int Get(int id)
        {
            return id;
        }
    }

    [ApiDescriptionSettings(Tag = "分组二")]
    public class TestAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        public int Get(int id)
        {
            return id;
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/tag1.png")} />

  </TabItem>
  <TabItem value="tag2">


```cs {5,19}
using Fur.DynamicApiController;

namespace Fur.Application
{
    [ApiDescriptionSettings(Tag = "合并所有标签")]
    public class FurAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        public int Get(int id)
        {
            return id;
        }
    }

    [ApiDescriptionSettings(Tag = "合并所有标签")]
    public class TestAppService : IDynamicApiController
    {
        public string Get()
        {
            return nameof(Fur);
        }

        public int Get(int id)
        {
            return id;
        }
    }
}
```

如下图所示：

<img src={useBaseUrl("img/tag2.png")} />

  </TabItem>
</Tabs>


:::tip 小知识

如果 `Tag` 名字一样，则会自动合并，否则只是命名。

:::

## 3.10 `DynamicApiControllerSettings` 配置

`Fur` 还提供动态 `WebAPI` 接口一些全局配置选项，如：

- `DefaultRoutePrefix`：默认路由前缀，`string`，默认 `api`
- `DefaultHttpMethod`：默认请求谓词，`string`，默认：`POST`
- `DefaultModule`：默认模块名称（区域），可用作接口版本，`string`，默认：`v1`
- `LowercaseRoute`：小写路由格式，`bool`，默认：`true`
- `KeepVerb`：是否保留动作谓词，`bool`，默认：`false`
- `CamelCaseSeparator`：骆驼命名分隔符，`string`，默认：`-`
- `VersionSeparator`：版本分隔符，`string`，默认：`@`
- `ModelToQuery`：`GET/HEAD` 请求将 `类类型参数转查询参数`，`bool`，默认 `false`
- `SupportedMvcController`：是否支持 `Mvc Controller` 动态配置，`bool`，默认 `false`
- `AbandonControllerAffixes`：默认去除控制器名称前后缀列表名，`string[]`，默认：
  - `AppServices`
  - `AppService`
  - `ApiController`
  - `Controller`
  - `Services`
  - `Service`
- `AbandonActionAffixes`：默认去除动作方法名称前后缀列表名，`string[]`，默认：
  - `Async`

### 3.10.1 支持 `Mvc 控制器` 动态配置

默认情况下，`Fur` 动态 `WebAPI` 接口不对 `ControllerBase` 类型进行任何处理。当然，我们也可以手动启用 `ControllerBase` 支持。

```json {2-4} title="Fur.Web.Entry/appsettings.json"
{
  "AppSettings": {
    "DynamicApiControllerSettings": {
      "SupportedMvcController": true
    }
  }
}
```

设置 `SupportedMvcController: true` 后，`Mvc ControllerBase` 类型也能和动态 `WebAPI` 一样的灵活了。代码如下：

```cs {5}
using Microsoft.AspNetCore.Mvc;

namespace Fur.Web.Entry.Controllers
{
    public class MvcController : ControllerBase
    {
        public string Get()
        {
            return nameof(Fur);
        }
    }
}

```

:::warning 注意事项

启用该配置后，如果 `Mvc 控制器` 没有任何 `[Route]` 特性，但是贴了 `[ApiController]` 特性将会报错。原因是 `[ApiController]` 特性内部做了路由特性检测。所以建议使用 `[ApiDataValidation]` 代替。

查看 [ASP.NET Core - ApiBehaviorApplicationModelProvider 源码](https://github.com/dotnet/aspnetcore/blob/c565386a3ed135560bc2e9017aa54a950b4e35dd/src/Mvc/Mvc.Core/src/ApplicationModels/ApiBehaviorApplicationModelProvider.cs#L90)

:::

## 3.11 反馈与建议

:::note 与我们交流

给 Fur 提 [Issue](https://gitee.com/monksoul/Fur/issues/new?issue)。

:::
